package util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.ToolProvider;
import util.simpleIO.Out;

/**
 * Collection of useful functions.
 * 
 * @author Krisi
 */
public class Tools {

	public static char SEPERATOR = '.';				// seperator, for numbers??
	public static char KOMMA	 = ',';				// the mathematical komma (e.g. ',' or '.')
	
	// ---- Sleep etc. ---- --------------------------------------------
	//
	
    /**
     * Sleep with try+catch.
     * Ignores values smaller than 1.
     * @return: true if error
     */
    static public boolean sleep(long ms)	{

    	if(ms<1)
    		return false;
    	
    	try{ 
    		Thread.sleep(ms);
    	}
    	catch(Exception e){
    		return true;		//error
    	}
    	return false;			//no error
    }
	//  /**
	//  * Sleep with try+catch and nanos
	//  * @return: true if error
	//  */
	// static public boolean sleep(long ms, int nanos)	{  					
	// 	try{ 
	// 		Thread.sleep(ms, nanos);		// 0-999999 additional nanoseconds to sleep.
	// 	}
	// 	catch(Exception e){
	// 		return true;		//error
	// 	}
	// 	return false;			//no error
	// }
    
	// ---- Strings and Numbers ---- --------------------------------------------
    //
    
	/** cuts out string from i1 to i2 (inclusive) **/
	public static String substringInverse(String str, int i1, int i2){
		String s = "";
		
		s += str.substring(0, i1);
		if(i2+1 < str.length())
			s += str.substring(i2+1, str.length());
		
		return s;
	}
	
	/**
	 * @param num... int array
	 * @return maximum int from array
	 */
	public static int max(int[] num){
		
		if(num==null || num.length<1){
			return -1;
		}
		int max = num[0];
		
		for(int i : num){
			if(i>max)
				max=i;
		}
		
		return max;
	}
	
	/**
	 * Splits all numbers out of an String 'str' and puts them into an String Array
	 * @param str
	 * @return String[] with numbers
	 */
	public static String[] numberSplit(String str) {
		
		ArrayList<String> parts = new ArrayList<String>(6);
		String temp="";

		for(int i=0; i<str.length(); i++){

			char c = str.charAt(i);

			if(Character.isDigit(c)){	//add digit
				temp+=c;
			}
			else{				//finish digit or ignore sign
				if(temp.length()>0){
					parts.add(temp);
					temp="";
				}
			}
		}
		
		if(temp.length()>0)		//add last or single number
			parts.add(temp);
		return parts.toArray(new String[1]);
	}
	
	/**
	 * Turns the String around. "abc" -> "cba"
	 * 
	 * @param s - String
	 * @return  - turned String
	 */
	public static String turnString(String s){
	
		StringBuffer sb = new StringBuffer(s.length());
		
		for(int i=s.length()-1; i>=0; i--){
			sb.append(s.charAt(i));
		}
		
		return sb.toString();
	}
	
	/**
	 * Get bytes from an Integer
	 * @param x	- integer
	 * @return	- byte[4], bytes from the Integer
	 */
	public static byte[] getBytes(int x){
		
		byte[] bA = new byte[4];
		
		for(int i=bA.length-1; i>=0; i--){	// 4 byte to shift
			
			bA[i] = (byte)x;
			x = x>>8;	// right shift 8 bit
		}
		
		return bA;
	}
	/**
	 * Fill bytes from Integer into an Array of the length 'length'
	 * (at the end of the new array)
	 * 
	 * @param x			- integer to split
	 * @param length	- length of return array
	 * @return			- byte[nrOfBytes], bytes from the Integer
	 */
	public static byte[] getBytes(int x, int length){
		
		byte[] bA1 = getBytes(x);
		byte[] bA2 = new byte[length];
		int len1 = bA1.length;
		int len2 = bA2.length;
		
		int i;
		for(i=0; i<len1 && i<len2; i++){	// copy
			bA2[len2-1-i] = bA1[len1-1-i];
		}
		//for(   ; i<len2; i++){				// fill up - zero anyway
		//	bA2[i] = 0;
		//}
		
		return bA2;
	}
	
	/**
	 * Turns the Byte Array
	 * @param bA1
	 * @return inversed byte[]
	 */
	public static byte[] invertBytes(byte[] bA1){
		
		int len = bA1.length;
		byte[] bA2 = new byte[len];
		
		for(int i=0; i<len; i++){
			bA2[i] = bA1[len-i-1]; 
		}
		
		return bA2;
	}
	
	/**
	 * Returns the Bits of an 'Long'
	 * 
	 * @param x - Long, 64 bit / 8 Byte
	 * @return  - Bits in String
	 */
	public static String getBitString(long x){
		
		String s = "";
		
		for(int i=0; i<64; i++){	// 64 bit to shift
			s += x&1;				// bit and
			x = x>>1;				// right shift one
			if(i%8==7 && i<63)		// place spaces:
				s+=" ";
		}
		return turnString(s);
	}
	/**
	 * Returns the Bits of an 'Integer'
	 * 
	 * @param x - Integer, 32 bit / 4 Byte
	 * @return  - Bits in String
	 */
	public static String getBitString(int x){
		
		String s = "";
		
		for(int i=0; i<32; i++){	// 32 bit to shift
			s += x&1;				// bit and
			x = x>>1;				// right shift one
			if(i%8==7 && i<31)		// place spaces:
				s+=" ";
		}
		return turnString(s);
	}
	/** 
	 * Returns the Bits of an 'byte' 
	 * 
	 * @param b - byte, 8 bit
	 * @return  - Bits in String
	 */
	public static String getBitString(byte b){
		
		String s = "";
		int x = b;		// must be int to shift
		
		for(int i=0; i<8; i++){		// 8 bit to shift
			s += x&1;	// bit and
			x = x>>1;	// right shift one
		}
		return turnString(s);
	}
	/**
	 * Returns the Bits of an Byte-Array
	 * 
	 * @param bA	- byte []
	 * @return		- Bit String
	 */
	public static String getBitString(byte[] bA){
		
		String s = "";						// empty String
		
		for(int i=0; i<bA.length; i++){		// for every Byte
			s += getBitString(bA[i]) + " ";		// save bits
		}
		return s;
	}
	
	
	/**
	 * Return Hexcode of one character
	 * 
	 * @param c
	 * @return
	 */
	public static String makeHex(char c){
		return "" + getOneHexSign((byte)(c/16)) + getOneHexSign((byte)(c%16));
	}
	
	/**
	 * Return Hexcode of the last 4 Bit of an Byte as String
	 * 
	 * @param b
	 * @return
	 */
	private static String getOneHexSign(byte b){
		
		if(b<=9){							// Number directly
			return ""+b;
		}
		else if(b<=15){						// A to F
			return "" + (char)('A'+ b);
		}
		else{								// too high
			throw new RuntimeException("Number >= 16");
		}
	}
	
	/**
	 * Returns a formatted double as String (separators every third digit).
	 * @param number		- number to process
	 * @return 				- formatted number as string
	 */
	public static String formatNumber(double number){
		return formatNumber(number, 0, 0, true, ' ');
	}
	
	/**
	 * Returns a formatted double as String.
	 * @param number		- number to process
	 * @param vkSize		- length for String before comma. Vorkommaanteil.
	 * @param nkSize		- length for String after comma.  Nachkommaanteil.
	 * @param putSepepators	- puts separator (global, static) all three numbers, for better readability
	 * @param padding		- padding sign. Filled in before vk and after nk
	 * @return 				- formatted number as string
	 */
	public static String formatNumber(double number, int vkSize, int nkSize, boolean putSepepators, char padding){
		
		String[] split = (""+number).split(".");	// split number string at comma
		if(split.length<1 || split.length>2)
			throw new RuntimeException("Should not happen...");
		
		StringBuffer vk, nk;								// Vor-Komma, Nach-Komma, def + init:
		vk = new StringBuffer(split[0]);
		if(split.length==2)
			nk =  new StringBuffer(split[1]);
		else
			nk =  new StringBuffer();
		
		if(putSepepators){									// Seperators:
			
			for(int i=vk.length()-3; i>0; i-=3){			// add seperators for "before komma":
				vk.insert(i, SEPERATOR);
			}
			for(int i=vk.length()-3; i>0; i-=3){			// add seperators for "after komma":
				vk.insert(i, SEPERATOR);
			}
		}

		while(vkSize<vk.length()) {							// add padding for "before komma":
			vk.insert(0, padding);
		}
		while(nkSize<nk.length()) {							// add padding for "after komma":
			nk.insert(0, padding);
		}
		
		String numberStr = vk.toString() + nk.toString();
		
		return numberStr;
	}
	
	/**
	 * Returns a formatted Long as String (separator every third digit)
	 * @param number
	 * @return
	 */
	public static String formatNumber(long number){
		
		//final char SEPERATOR 	= '.';				// a seperator ('.')
		final String numberStr  = "" + number;		// the number to parse
		
		StringBuffer numBuff = new StringBuffer(numberStr);	// StringBuffer for return number

		for(int i=numberStr.length()-3; i>0; i-=3){			// Set seperators:
			numBuff.insert(i, SEPERATOR);
		}
		
		return numBuff.toString();				// Return formatted number
	}
	
	/**
	 * Returns a String with an formatted size (KB, MB, GB, ...)
	 * 
	 * @param size in byte
	 * @return Size in appropriate form.
	 */
	public static String formatBytes(long size){
		
		//final char SEPERATOR = '.';			// a seperator ('.')
		//final char KOMMA	 = ',';				// the mathematical komma (',')
		
		final String number  = "" + size;		// the size to parse
		
		int dim = (number.length()-1)/3;					// 'dimension' 10^3, 10^6, 10^9, ...
		int div=dim*3;										// Divisor. Not really needed?
		StringBuffer typeBuff = new StringBuffer(" Byte");	// Buffer for return Type (KB, MB, GB, ...)
		
		switch(dim){										// --- Get correct extension: ---
			case 0: /* nix */               break;
			case 1: typeBuff.insert(1, 'K'); break;
			case 2: typeBuff.insert(1, 'M'); break;
			case 3: typeBuff.insert(1, 'G'); break;
			case 4: typeBuff.insert(1, 'T'); break;
			case 5: typeBuff.insert(1, 'P'); break;
			default: typeBuff.insert(1, '_'); div=0; 
		}
		
		StringBuffer numBuff = new StringBuffer(number);	// StringBuffer for return number
		
		if(div == 0){										// --- Just some bytes. return number+type: ---
			// nix
		}
		else{												// --- Put seperator and komma: ---
			
			for(int i=number.length()-3; i>0; i-=3){			// add seperators:
				numBuff.insert(i, SEPERATOR);
			}
			
			for(int i=1; i<numBuff.length(); i++){				// Set komma:
				if(numBuff.charAt(i)==SEPERATOR){
					numBuff.deleteCharAt(i);
					numBuff.insert(i, KOMMA);
					break;
				}
			}
		}
		
		return numBuff.toString() + typeBuff;				// Return number and Type
	}
	
	/** @return true if the 'value' is within 'baseValue' +- 'tolerance'  **/
	public static boolean within(double baseValue, double tolerance, double value){
		
		if(tolerance<0)
			tolerance *= -1;
		
		return value>(baseValue-tolerance) && value<(baseValue+tolerance);
	}
	
	/**
	 * @param d - double
	 * @return String with ',' instead of '.'
	 */
	public static String otherKomma(double d, char komma){
		
		StringBuffer n2 = new StringBuffer(8);
		String number   = "" + d;
		
		for(int i=0; i<number.length(); i++){
			char c = number.charAt(i);
			
			if(c == '.')
				n2.append(komma);
			else
				n2.append(c);
		}
		
		return n2.toString();
	}
	public static String excelKomma(double d){
		return otherKomma(d, ',');
	}
	
	/**
	 * Normal rounding. 
	 *   0.1 -> 0
	 *   0.5 -> 1
	 *   0.9 -> 1
	 * @param f
	 * @return
	 */
	public static int round(float f){
		return (int) (f+0.5f);
	}
	/**
	 * Round up. 
	 *   0.1 -> 1
	 *   0.5 -> 1
	 *   1.001 -> 2
	 * @param f
	 * @return
	 */
	public static int roundUp(float f){
		return (int) (f+1f);
	}
	
	/**
	 * Use Byte unsigned as Integer. (-1 = highest value)
	 * @param num - byte, 8 bit
	 * @return Integer from all 8 bit. No sign.
	 */
	public static int unsignedByteToInt(byte num){
		
		//Out.pl(Tools.getBits(b) + " ...number");
		//Out.pl(Tools.getBits(b&0xFF) + " ...number");
		
		return (num & 0xFF);		// why??
	}
	
	/**
	 * Use Integer unsigned as Long. (-1 = highest value)
	 * @param num - Integer, 32 bit
	 * @return Long from all 32 Bit. No sign.
	 */
	public static long unsignedIntToLong(int num){
		
		long mask = 0x80000000l;		// Bit 32 is set
		
		long L;
		if(num<0)						// conversion of negative number (take first bit + put back)
			L = (int)(num & ~mask) | mask;
		else							// direct conversion for positive number (first bit is zero)
			L = num;
		
		//Out.pl(Tools.getBits(num) + " ...number");
		//Out.pl(Tools.getBits(L)   + " ...end result Long");
		
		return L;
	}
	
	// ---- System and Memory ---- --------------------------------------------
	//
	
	/**
	 * Print system information
	 * @param doGarbageCollect
	 */
	public static void systemInfo(boolean doGarbageCollect){
		
		if(doGarbageCollect)
			Runtime.getRuntime().gc();						// garbage collect
		long freeMemH = Runtime.getRuntime().freeMemory();	// get free memory
		long heapSize = Runtime.getRuntime().totalMemory();	// get heap memory
		long maxMem   = Runtime.getRuntime().maxMemory();	// get max memory
		long usedMem  = (heapSize-freeMemH);				// used memory
		long freeMem  = (maxMem-usedMem);					// total free memory
		int  cores    = Runtime.getRuntime().availableProcessors();	// CPU cores
		
		Out.pl("CPU cores           : " + rightAlign(formatNumber(cores) ,14) );
		Out.pl();
		Out.pl("Used Memory         : " + rightAlign(formatNumber(usedMem) ,14) );
		Out.pl("Free Memory         : " + rightAlign(formatNumber(freeMem) ,14) );		/*
		Out.pl("Free Memory in Heap : " + rightAlign(formatNumber(freeMemH) ,14) );		*/
		Out.pl("Heap size           : " + rightAlign(formatNumber(heapSize),14) );
		Out.pl("Maximum Memory      : " + rightAlign(formatNumber(maxMem)  ,14) );
	}
	
	/**
	 * Get total free memory for java.
	 * @param doGarbageCollect
	 * @return free memory, long
	 */
	public static long getFreeMem(boolean doGarbageCollect){
		if(doGarbageCollect)
			Runtime.getRuntime().gc();						// garbage collect
		long freeMemH = Runtime.getRuntime().freeMemory();	// get free memory
		long heapSize = Runtime.getRuntime().totalMemory();	// get heap memory
		long maxMem   = Runtime.getRuntime().maxMemory();	// get max memory
		long usedMem  = (heapSize-freeMemH);				// used memory
		long freeMem  = (maxMem-usedMem);					// total free memory
		
		return freeMem;
	}
	
	/**
	 * Throws RuntimeException if size is bigger than the available Memory.
	 * @param size
	 * @return available memory in Bytes. 1 MB tolerance subtracted for overhead etc.
	 */
	public static long testAvailableRam(long size){
		//Tools.systemInfo(false);
		long freeMem = getFreeMem(true) - 1024*1024;	// actual free memory minus 1MB overhead and tolerance
		if(size > freeMem)
			throw new RuntimeException("Not enough Memory! Only "+freeMem+" available.");
		
		return freeMem;
	}
	/**
	 * Throws RuntimeException if size is bigger than the available Memory.
	 * OR if size is bigger than Integer.MAX_VALUE.
	 * Uses 'long testAvailableRam(long size)'
	 * @param size
	 * @return available memory in Bytes. 1 MB tolerance subtracted for overhead etc.
	 */
	public static int testAvailableRamInt(long size){
		//Tools.systemInfo(false);
		long freeMem = testAvailableRam(size);
		if(size > Integer.MAX_VALUE)
			throw new RuntimeException("Requested Memory too big for an Integer! Only "+Integer.MAX_VALUE+" possible.");
		
		return (int)freeMem;
	}
	
	/**
	 * Get whole file from target.
	 * Reads a real File into the RAM.
	 * @param fileStr		- file with path+name
	 * @throws IOException	- may be thrown
	 */
	public static byte[] readFileToArray(String fileStr) throws IOException{
		
		File file = new File(fileStr);						// get file information
		
		long sizeL = file.length();							// get file length
		Tools.testAvailableRamInt(sizeL);					// test length and available ram (smaller Integer.MAX_VALUE)
		int  size  = (int) sizeL;							// long to int
		
		FileInputStream in = new FileInputStream(file);		// make input stream for reading
		byte[] data = new byte[size];						// make array for data
		in.read(data);										// read file data
		in.close();											// close input stream
		
		return data;
	}
	
	
	// ---- String alignment ---- --------------------------------------------
	//
	
	/**
	 * Aligns string s on the right in space 'space'.
	 * Fills left side with spaces.
	 * 
	 * @param s
	 * @param space
	 * @return aligned String
	 */
    static public String rightAlign(String s, int space)	{ 
    	
    	StringBuffer b = new StringBuffer(space+8);
    	int spaces=space-s.length(); 
    	for(int i=0; i<spaces; i++) 
    		b.append(' '); 
    	b.append(s); 
    	return b.toString();
    }
    /**
     * See 'rightAlign(String s, int space)'
     * @param v
     * @param space
	 * @return aligned String
     */
    static public String rightAlign(Object v, int space)	{ 	return rightAlign(v.toString(), space);	}

    
	// ---- Classes and Compiling ---- --------------------------------------------
	//
	// See: util.classLoading . ClassLoading.java
}

